#pragma once

#include "Thread.h"


#include <memory>
#include <set>
#include <deque>
#include <algorithm>

typedef void (WINAPI *PWORKER_THREAD_ROUTINE)(
	LPVOID lpThreadParameter
	);

/*
Logic:

Pool:
 List of threads
 List of available threads counter

Actions:
 Add a new task:
   -if pool of available is empty 
	 if pool has not reached max
	   - create new thread and run	
	 otherwise 
	   - add task and forget
   -if available pool is not empty
	 - put data into queue
	 - signal thread
   
 Changes minimum number of threads
	  Creates threads and puts them into wait
 Changes in maximum number of threads
	  Uses thread life management routines

 Thread life management
	- there is event that is signaled that thread has to stop
	- Thread stops and deletes itself

   Notes:
	  All ref counting logic has to be synchronized. 

   SuspendThread is not good for synchronization issues



*/

namespace Common
{
namespace System
{
namespace Threading
{

class CSimpleWorkerPool
{
public:
	CSimpleWorkerPool(bool bAllocateMinPool = false, size_t szMinThreads = 0, size_t szMaxThreads = 0, bool bAutoClose = true) :
		m_szMaxThreads(szMaxThreads),
		m_bAutoclose(bAutoClose)
	{
		m_hEvents[EVENT_NEXT_TASK] = ::CreateEvent(NULL, FALSE, FALSE, NULL);
		m_hEvents[EVENT_EXIT_POOL] = ::CreateEvent(NULL, FALSE, FALSE, NULL);

		if (0 == szMaxThreads)
		{
			SYSTEM_INFO info;
			::GetSystemInfo(&info); 
			m_szMaxThreads = 2 < info.dwNumberOfProcessors ? 2 : info.dwNumberOfProcessors;
		}

		if (szMinThreads > m_szMaxThreads)
		{
			m_szMaxThreads = szMinThreads;
		}

		
		SetMinThreadNumber(szMinThreads, bAllocateMinPool);

		
	}

	void AddTask(PWORKER_THREAD_ROUTINE pFunc, LPVOID pData, HANDLE hFinish = NULL)
	{
		AppendTask(pFunc, pData, hFinish);
		ExecuteNextTask();
	}

	//Adds task to front of the queue
	void AddHighPriorityTask(PWORKER_THREAD_ROUTINE pFunc, LPVOID pData, HANDLE hFinish = NULL)
	{
		AppendTask(pFunc, pData, hFinish, false);
		ExecuteNextTask();
	}

	~CSimpleWorkerPool() 
	{
		if(m_bAutoclose)
		{
			size_t currentSize(0);
			m_scPool.Lock();
			currentSize = m_arrThreads.size();
			m_scPool.Unlock();
			while(0 < currentSize)
			{
				::SetEvent(m_hEvents[EVENT_EXIT_POOL]);
				m_scPool.Lock();
				currentSize = m_arrThreads.size();
				m_scPool.Unlock();
			}
		}
	}
protected:
	//Inner classes
	struct TPair
	{
		TPair(PWORKER_THREAD_ROUTINE pFunc = NULL, LPVOID pData = NULL, HANDLE hFinish = NULL)
		{
			this->pFunc = pFunc;
			this->pData = pData;
			this->hFinishEvent = hFinish;
		}
		LPVOID  pData;
		PWORKER_THREAD_ROUTINE pFunc;
		HANDLE hFinishEvent;
	};
	class CWorkerThread : public CThreadImpl<CWorkerThread>
	{
	public:
		CWorkerThread(CSimpleWorkerPool * pParent) : m_pParent(pParent)
		{
			ATLASSERT(NULL != pParent);
		}

		void SetPointer(std::shared_ptr<CWorkerThread> & ptrThis)
		{
			m_thisPtr = ptrThis;
		}
		//override
		DWORD Run()
		{
			
			DWORD dwEvent;
			do
			{
				auto value = m_pParent->PopNexTask() ;
				if (NULL != value && NULL != value->pFunc)
				{
					std::shared_ptr<CWorkerThread> thisTread(m_thisPtr);
					m_pParent->RemoveFreeThread(thisTread);
					value->pFunc(value->pData);
					if (NULL != value->hFinishEvent)
					{
						::SetEvent(value->hFinishEvent);
					}
				}
				else
				{
					{
						std::shared_ptr<CWorkerThread> thisTread(m_thisPtr);
						m_pParent->AddFreeThread(thisTread); //register itself as available
					}
					dwEvent = ::WaitForMultipleObjects(2, m_pParent->m_hEvents, FALSE, INFINITE);
					switch (dwEvent)
					{
					case EVENT_NEXT_TASK: //There is a new task!!!!						
						break;
					case EVENT_EXIT_POOL:
						{
							std::shared_ptr<CWorkerThread> thisTread(m_thisPtr);;
							if(m_pParent->RemoveThread(thisTread))
								return 0 ;
						}
						break;
					default: //Not clear if it should kill itself if error happens
						break;
					}
				}
				
			} while (true);
		}

	private:
		CSimpleWorkerPool * m_pParent;
		std::weak_ptr<CWorkerThread> m_thisPtr;
	};
private:  //functions
	std::shared_ptr<TPair> PopNexTask()
	{
		std::shared_ptr<TPair> value;
		m_scTasks.Lock();
		if(!m_lstTasks.empty())
		{
			value = m_lstTasks.back();
			m_lstTasks.pop_back();
		}
		m_scTasks.Unlock();
		return value;
	}
	
	void AppendTask(PWORKER_THREAD_ROUTINE pFunc , LPVOID pData,  HANDLE hFinish = NULL, bool last = true)
	{
		std::shared_ptr<TPair> task = std::make_shared<TPair>(pFunc, pData, hFinish);
		m_scTasks.Lock();
		if (last)
		{
			m_lstTasks.push_front(task);
		}
		else
		{
			m_lstTasks.push_back(task);
		}
		m_scTasks.Unlock();
	}

	BOOL RemoveThread(std::shared_ptr<CWorkerThread> & thread)
	{
		BOOL bResult(FALSE);
		RemoveFreeThread(thread);
		m_scPool.Lock();
		auto ptr = m_arrThreads.find(thread);
		if (m_arrThreads.end() != ptr)
		{
			bResult =  
				m_arrThreads.end() == m_arrThreads.erase(ptr);
		}
		
		m_scPool.Unlock();
		return bResult;
	}

	BOOL RemoveFreeThread(std::shared_ptr<CWorkerThread> & thread)
	{
		ATLASSERT(NULL != thread);
		BOOL bResult(FALSE);
		m_scPoolFree.Lock();
		auto ptr = m_arrpAvailableThreads.find(thread);
		if(m_arrpAvailableThreads.end() != ptr)
		{
			bResult =  m_arrpAvailableThreads.end() == m_arrpAvailableThreads.erase(ptr);
		}
		m_scPoolFree.Unlock();
		return bResult;
	}
	void AddFreeThread(std::shared_ptr<CWorkerThread> &  thread)
	{
		ATLASSERT(NULL != thread);
		m_scPoolFree.Lock();
		m_arrpAvailableThreads.insert(thread);
		m_scPoolFree.Unlock();
	}

	void ExecuteNextTask()
	{
		//Task scheduling happens here
		DWORD dwSize(0);
		
		if(m_arrpAvailableThreads.size() > 0)
		{
			m_scPoolFree.Lock();
			if (m_arrpAvailableThreads.size())
			{
				::SetEvent(m_hEvents[EVENT_NEXT_TASK]);
			}
			m_scPoolFree.Unlock();
			return;
		}
		
		if (m_szMaxThreads > m_arrThreads.size())
		{
			AddThreadToPool(true);
		}
		
	}

	void AddThreadToPool(bool growToMax = false)
	{
		m_scPool.Lock();
		size_t size (m_arrThreads.size()); 
		if(size < m_szMinThreads || (growToMax && size < m_szMaxThreads))
		{
			int nMin(m_szMinThreads - m_arrThreads.size());
			do
			{
				std::shared_ptr<CWorkerThread> thread = std::make_shared<CWorkerThread> (this);
				thread->SetPointer(thread);
				m_arrThreads.insert(thread);
				thread->Start();
			} while (0 < nMin--);
		}
		m_scPool.Unlock();
	}

public:
	//Accessors
	size_t GetThreadNumber() { return m_arrThreads.size();}

	size_t GetFreeThreadNumber() { return m_arrThreads.size();}

	size_t GetMaxThreadNumber () { return m_szMaxThreads;}

	size_t GetMinThreadNumber () { return  m_szMinThreads;}

	//Set maximum pool size. 
	//If minimum is larger than max then minimum is used	
	size_t SetMaxThreadNumber (size_t size) 
	{		
		m_scPool.Lock();
		
		if(size < m_szMinThreads )
			m_szMaxThreads = m_szMinThreads;
		else
			m_szMaxThreads  = size;
		size = m_szMaxThreads;
		
		m_scPool.Unlock();
		return size;
	}

	void SetMinThreadNumber (size_t size, bool create = false) 
	{
		m_scPool.Lock();
		
		if(size > m_szMaxThreads)
			m_szMaxThreads = size;
		m_szMinThreads = size;
		AddThreadToPool();
		
		m_scPool.Unlock();
	}

private: // variables

	//Number of threads
	typedef std::set<std::shared_ptr<CWorkerThread> > ArrayThread;
	ArrayThread m_arrThreads;
	ArrayThread m_arrpAvailableThreads;
	//List of tasks
	std::deque<std::shared_ptr<TPair> > m_lstTasks;

	CComAutoCriticalSection m_scTasks;
	CComAutoCriticalSection m_scPool;
	CComAutoCriticalSection m_scPoolFree;

	size_t m_szMinThreads;
	size_t m_szMaxThreads;

	bool m_bAutoclose;

	HANDLE m_hEvents[2];
	const static int EVENT_NEXT_TASK = WAIT_OBJECT_0;
	const static int EVENT_EXIT_POOL = WAIT_OBJECT_0 + 1;

};

typedef std::shared_ptr<Common::System::Threading::CSimpleWorkerPool> ThreadPoolPtr;
} // namespace Threading
} // namespace System
} // namespace Common

